package com.fanshuai.soring.boot.filter;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.filter.AbstractRequestLoggingFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Map;

@Slf4j
public class LogFilter extends AbstractRequestLoggingFilter {

    @Override
    protected boolean shouldLog(HttpServletRequest request) {
        Object shouldLog = request.getAttribute("should-log");
        return null != shouldLog && (boolean) shouldLog;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        FilterParams params = new FilterParams(request, response, filterChain);

        try {
            beforeFilter(params);
            execute(params);
        } catch (Exception e) {
            log.error("log error, ", e);
        } finally {
            afterFilter(params);
        }
    }

    @Override
    protected void beforeRequest(HttpServletRequest request, String message) {
        log.info("request={}", message);
    }

    @Override
    protected void afterRequest(HttpServletRequest request, String message) {
        String requestMessage = createRequestMessage(request);
        log.info("request={}, response={}", requestMessage, message);
    }

    protected void beforeFilter(FilterParams params) {
        long start = System.currentTimeMillis();
        params.getRequest().setAttribute("startTime", start);

        ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(params.response);
        responseWrapper.setContentLength(getMaxPayloadLength());
        params.setResponse(responseWrapper);

        boolean firstRequest = !isAsyncStarted(params.request);
        HttpServletRequest requestToUse = params.request;
        if (firstRequest && !(params.request instanceof ContentCachingRequestWrapper)) {
            requestToUse = new ContentCachingRequestWrapper(params.request, getMaxPayloadLength());
        }
        params.setRequest(requestToUse);

        if (firstRequest && shouldLog(requestToUse)) {
            beforeRequest(requestToUse, createRequestMessage(requestToUse));
        }
    }

    protected void execute(FilterParams params) throws IOException, ServletException {
        params.filterChain.doFilter(params.request, params.response);
    }

    protected void afterFilter(FilterParams params) throws IOException {
        boolean firstRequest = !isAsyncStarted(params.request);
        if (firstRequest && shouldLog(params.request)) {
            afterRequest(params.request, createResponseMessage(params.request, params.response));
        }

        if (params.response instanceof ContentCachingResponseWrapper) {
            ((ContentCachingResponseWrapper) params.response).copyBodyToResponse();
        }
    }

    private String createRequestMessage(HttpServletRequest request) {
        StringBuilder b = new StringBuilder();

        b.append(request.getMethod()).append(" ").append(request.getRequestURI());

        if (StringUtils.isNotBlank(request.getQueryString())) {
            b.append("?").append(request.getQueryString());
        }

        Map<String, String[]> params = request.getParameterMap();
        for (String key : params.keySet()) {
            b.append(String.format("%s=%s&", key, params.get(key)[0]));
        }

        b.append(" headers=");
        Enumeration<String> headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String headerName = headerNames.nextElement();
            String header = request.getHeader(headerName);

            b.append(String.format("%s=%s ", headerName, header));
        }

        String client = request.getRemoteAddr();
        String user = request.getRemoteUser();
        HttpSession session = request.getSession();

        b.append(" remoteAddr=").append(client);
        if (StringUtils.isNotBlank(user)) {
            b.append(" user=").append(user);
        }
        if (null != session) {
            b.append(" session=").append(session.getId());
        }

        String payload = getMessagePayload(request);
        if (StringUtils.isNotBlank(payload)) {
            b.append("payload=").append(payload);
        }

        return b.toString();
    }

    private String createResponseMessage(HttpServletRequest request, HttpServletResponse response) {
        if (!(response instanceof ContentCachingResponseWrapper)) {
            return "";
        }

        ContentCachingResponseWrapper responseWrapper = (ContentCachingResponseWrapper) response;
        byte[] body = responseWrapper.getContentAsByteArray();

        StringBuilder b = new StringBuilder();

        int length = Math.min(Integer.MAX_VALUE, getMaxPayloadLength());
        String s = new String(body, 0,  length, StandardCharsets.UTF_8);
        b.append("body=").append(s);

        long start = (long) request.getAttribute("startTime");
        b.append(" spend times=").append(System.currentTimeMillis() - start).append("ms");

        return b.toString();
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class FilterParams {
        HttpServletRequest request;
        HttpServletResponse response;
        FilterChain filterChain;
    }
}
